import _ from "lodash";
import { RenderTools } from "./renderTools";
import { isInitArray, isInitNumber } from "../../../../tools/index";

let spriteSeedId = 0;

export class SpriteNode {

  id = spriteSeedId++;

  listen = false;

  key: any = undefined

  root: any = undefined;

  parent: SpriteNode | undefined = undefined;

  children: Array<SpriteNode> = [];

  panel = document.createElement("canvas");

  tools: RenderTools;

  rect: DOMRect | undefined = undefined;

  relativeRect: DOMRect | undefined = undefined;

  config: any = {};

  isChanged = true;

  isLeaf = false;

  isReferenceRoot = false

  zIndex: number = 0;

  constructor(config: any) {
    this.config = config;

    if (typeof config["key"] === "string") {
      this.key = config["key"]
    }

    if (typeof config["listen"] === "boolean") {
      this.listen = config["listen"]
    }

    if (typeof config["root"] === "boolean") {
      this.isReferenceRoot = config["root"]
    }

    this.tools = new RenderTools(this.panel);
  }

  // 设置相对父级的路径
  setRect(rect: any) {
    if (rect instanceof DOMRect) {
      if (this.rect instanceof DOMRect) {
        const arr: Array<"height" | "width"> = ["height", "width"]
        for (let index = 0; index < arr.length; index++) {
          const key = arr[index];
          if (this.rect[key] !== rect[key]) {
            this.isChanged = true
            break;
          }
        }
        this.rect = rect
      } else {
        this.rect = rect
        this.isChanged = true
      }
      if (this.rect instanceof DOMRect) {
        this.panel.width = this.rect.width;
        this.panel.height = this.rect.height;
        const left = this.rect.left + 'px'
        const top = this.rect.top + 'px'
        if (this.panel.style["left"] !== left) {
          this.panel.style["left"] = left
        }
        if (this.panel.style["top"] !== top) {
          this.panel.style["top"] = top
        }
        this.updateRelativeRect()
      }
    }
  }

  setRectValue(key: "x" | "y" | "height" | "width", value: number) {
    if (this.rect instanceof DOMRect) {
      if (key === "width" || key === "height") {
        if (this.rect[key] !== value) {
          this.isChanged = true
        }
      }
      this.rect[key] = value
      if (this.rect instanceof DOMRect) {
        this.panel.width = this.rect.width;
        this.panel.height = this.rect.height;
        const left = this.rect.left + 'px'
        const top = this.rect.top + 'px'
        if (this.panel.style["left"] !== left) {
          this.panel.style["left"] = left
        }
        if (this.panel.style["top"] !== top) {
          this.panel.style["top"] = top
        }
        this.updateRelativeRect()
      }
    }
  }

  updateRelativeRect() {
    if (this.rect instanceof DOMRect) {
      let { left, top } = this.rect
      const { width, height } = this.rect
      let node = this.parent;
      while (node !== this.root) {
        if (node instanceof SpriteNode) {
          if (node.rect) {
            left += node.rect.left
            top += node.rect.top
            if (node.isReferenceRoot) {
              break;
            }
          }
          node = node.parent
        } else {
          // 无法向上查找，直接定到根节点
          node = this.root
        }
      }
      this.relativeRect = new DOMRect(left, top, width, height)
    }
  }

  getImageData() {
    try {
      if (this.panel instanceof HTMLCanvasElement) {
        const ctx = this.panel.getContext("2d");
        if (isInitNumber(this.panel.width) && isInitNumber(this.panel.height)) {
          return ctx?.getImageData(0, 0, this.panel.width, this.panel.height);
        }
      }
    } catch (error) {
      console.log(error)
    }
  }

  appendChild(node: SpriteNode) {
    this.children.push(node)
  }

  render(initiate?: Boolean) {
    if (this.panel instanceof HTMLCanvasElement) {
      let update = this.isChanged;

      if (isInitArray(this.children)) {
        this.children.forEach((item: SpriteNode) => {
          if (typeof item.render === "function") {
            const childUpdate = item.render(initiate);
            update = update || childUpdate;
          }
        });
      }

      if (update || initiate) {
        try {
          this.tools.clear();
          let actions = this.config["actions"];
          if (typeof actions === "function") {
            actions = actions(this.root.state, this.rect);
          }
          if (isInitArray(actions)) {
            actions.forEach((action: any) => this.tools.run(action));
          }
          if (isInitArray(this.children)) {
            this.children.forEach((node: any) => this.tools.mergeImageData(node.getImageData(), node.rect));
          }
        } catch (error) {
          console.log(error);
        }
        this.isChanged = false;
        return true;
      }
    }
    return false;
  }
}

export class SpriteRoot {

  nodes: Array<SpriteNode> = [];

  nodeList: Array<SpriteNode> = [];

  state: any = {};

  stateListenMapping: any = {};

  dataListenMapping: any = {};


  designWidth = window["designWidth"];

  designHeight = window["designHeight"];

  width: number = 0

  height: number = 0

  nextTickTimer: any = undefined;

  setState(data: any, options?: any) {
    // 遍历监听值
    const defaults = {
      render: true,
      force: false
    }
    options = Object.assign(defaults, options)

    let ids: any = [];
    Object.keys(data).forEach((key) => {
      // 值相同，不更新事件
      if (!options.force && this.state[key] === data[key]) {
        return;
      }
      // 值不同，记录需要改变的node-id列表
      this.state[key] = data[key];
      const list = this.stateListenMapping[key];
      if (isInitArray(list)) {
        ids = ids.concat(list);
      }
    });
    _.uniq(ids).forEach((id) => {
      const node: any = this.nodeList.find((node: any) => node.id === id);
      if (node) {
        // 定位到node元素并设置isChanged为true
        node.isChanged = true;
      }
    });
    this.updateRect();
    // 调用渲染事件
    if (options.render) {
      clearTimeout(this.nextTickTimer)
      this.nextTickTimer = setTimeout(this.render.bind(this));
    }
  }

  setData(data: any) {
    if (this.panel) {
      const nodeIds: Array<number> = []
      const loop = (data: Array<any> | { children: any; watch: any }, index?: number): any => {
        if (_.isObject(data)) {
          if (Array.isArray(data)) {
            return data.map(loop);
          } else {
            const node = this.initNode(data);
            if (!(typeof node.zIndex === "number") && typeof index === "number") {
              node.zIndex = index
            }
            node.root = this;
            if (isInitArray(data.children)) {
              node.children = loop(data.children);
              node.children.sort((l: any, r: any) => l.zIndex - r.zIndex);
              node.children.forEach((child: SpriteNode) => child.parent = node);
              delete data["children"]
            } else if (typeof data.children === "object") {
              const { watch, actions } = data.children
              const list = this.state[watch]
              if (Array.isArray(list)) {
                if (!this.stateListenMapping[watch]) {
                  this.stateListenMapping[watch] = [];
                }
                node.children = list.map((item, index) => {
                  const child = this.initNode({
                    key: `$SpriteNode-array-${node.id}-${index}`,
                    rect: item.rect,
                    actions: () => actions(item)
                  });
                  child.parent = node
                  this.stateListenMapping[watch].push(child.id);
                  return child
                })
              }
            } else {
              node.isLeaf = true;
            }
            if (isInitArray(data.watch)) {
              data.watch.forEach((value: any) => {
                if (!this.stateListenMapping[value]) {
                  this.stateListenMapping[value] = [];
                }
                this.stateListenMapping[value].push(node.id);
              });
            }
            this.addNode(node);
            nodeIds.push(node.id)
            return node;
          }
        }
      }

      const newNodes = loop(data)
      const newKeys = new Set(newNodes.map((item: any) => item.id));

      const fragment = document.createDocumentFragment();
      for (const node of this.nodes) {
        if (!newKeys.has(node.id)) {
          this.panel.removeChild(node.panel);
        }
      }
      for (const node of newNodes) {
        fragment.appendChild(node.panel);
      }

      this.panel.appendChild(fragment)
      this.nodes = newNodes;

      this.updateFrame()

      this.nodeList = _.intersectionWith(this.nodeList, nodeIds, (l, r) => l.id === r)
      this.updateRect();
      this.render(true);
    }
  }

  updateFrame() {
    if (this.panel && this.screen) {
      const rootRect = this.screen.getBoundingClientRect()
      const { width, height } = rootRect
      const scaleX = width / this.designWidth
      const scaleY = height / this.designHeight
      const scale = Math.min(scaleX, scaleY)

      const left = (scale - 1) / 2 * width + "px"
      const top = (scale - 1) / 2 * height + "px"

      const value = `translateZ(0px) translate(${left},${top}) scale(${scale})`
      if (this.panel.style["transform"] !== value) {
        this.panel.style["transform"] = value
      }
    }
  }

  nodeListen(point: any) {
    const { x, y } = point
    const list: Array<any> = []
    this.nodeList.forEach((node: SpriteNode) => {
      if (node.listen && node.relativeRect) {
        const { left, right, top, bottom } = node.relativeRect
        if (x > left && x < right && y > top && y < bottom) {
          list.push({
            key: node.key,
            x: x - left,
            y: y - top,
            target: node
          })
        }
      }
    })

    return list
  }

  panel: HTMLDivElement | undefined = undefined;

  screen: HTMLDivElement | undefined = undefined;

  constructor(initialSetting: any) {
    // 设置初始配置值
    Object.keys(initialSetting).forEach((key: any) => _.set(this, key, initialSetting[key]));
  }

  init(initialData: any, initialState: any) {
    if (Array.isArray(initialData)) {
      // 根据data遍历生成组件
      this.state = Object.assign(this.state, initialState);
      this.setData(initialData)
    }
  }

  initNode(data: any) {
    const node = this.nodeList.find(node => {
      if (node.config === data) {
        return true
      }
      if (typeof data["key"] === "string") {
        if (node.key === data["key"]) {
          node.config = data
          return node
        }
      }
    })
    if (node instanceof SpriteNode) {
      return node
    }
    return new SpriteNode(data);
  }

  addNode(newNode: SpriteNode) {
    _.remove(this.nodeList, node => node.id === newNode.id)
    this.nodeList.push(newNode);
  }

  appendChild(id: number, data: any) {
    const node = this.nodeList.find(node => node.id === id)
    if (node instanceof SpriteNode) {
      const child = this.initNode(data)
      node.appendChild(child)
    }
  }

  updateRect() {
    const { designWidth, designHeight } = this;
    const rootRect = new DOMRect(0, 0, designWidth, designHeight);

    const loop = (
      item:
        | Array<any>
        | {
          rect: any;
          setRect: Function;
          isChanged: Boolean;
          isLeaf: Boolean;
          children: Array<any>;
        },
      parentRect: any,
      update?: Boolean
    ) => {
      if (_.isObject(item)) {
        if (Array.isArray(item)) {
          item.forEach((item) => loop(item, parentRect));
        } else {
          if (!item.rect) {
            // 空值先初始化区域
            item.setRect(parentRect);
          }
          update = update || item.isChanged;
          const rectFunc: any = _.get(item, "config.rect");
          if (update && typeof rectFunc === "function") {
            // 存在更新事件，触发区域更新
            const isRoot = _.get(item, "config.root");
            const rectPush = isRoot ? rootRect : parentRect;
            const resultRect = rectFunc(this.state, rectPush);
            item.setRect(resultRect);
          }
          if (!item.isLeaf) {
            loop(item.children, item.rect, update);
          }
        }
      }
    };
    loop(this.nodes, rootRect);
  }

  render(initiate?: Boolean) {
    let update = false;
    this.nodes.forEach((node: SpriteNode) => {
      const nodeUpdate = node.render(initiate);
      update = update || nodeUpdate;
    });
  }
}
